/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.html;


import com.blandware.atleap.common.util.DateUtil;
import com.blandware.atleap.webapp.util.core.ApplicationResources;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.taglib.TagUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Locale;

/**
 * <p>This tag renders javascript to show calendar popup window with different options.
 * Selected date will be stored into form field with given name.
 * </p>
 * <p>
 * You must include link to JS file with calendar functions somewhere on your page
 * </p>
 * <p>
 * Body of this tag will generate body of a link.
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>fieldName</b> - full name of input field to put selected date in. Must be
 * correct JavaScript name such as follows: <code>document.forms[0].dateInput</code>
 * </li>
 * <li>
 * <b>anchorName</b> - name of anchor that will be generated for calendar popup.
 * By default it is &quot;jsCalendarAnchor&quot; plus ordinal number (positive
 * integer leading by zero) of this tag on current page
 * </li>
 * <li>
 * <b>datePattern</b> - pattern for displaying date. By default it is taken from
 * locale stored in session under <code>org.apache.struts.Globals.LOCALE_KEY</code>
 * Date pattern taken has only date symbols, not time symbols
 * </li>
 * <li>
 * <b>startDate</b> - Pre-selected start date in calendar according to specified
 * date pattern. By default current date is preselected (it is calendar default)
 * </li>
 * <li>
 * <b>style</b> - Style of calendar displaying. Valid values are WINDOW and DIV
 * (manner is case insensitive). Default value is WINDOW
 * </li>
 * <li>
 * <b>divStyleId</b> - ID of 'div' tag for DIV-style display of calendar. This
 * attribute is optional if style was set to DIV, otherwise it is ignored. If
 * DIV style was set and this attribute is omitted, default value will be
 * &quot;jsCalendarDiv&quot;. Tag will be generated once, despite on number of
 * calendar tags on current page
 * </li>
 * <li>
 * <b>cssPrefix</b> - CSS prefix to search CSS classes for displaying calendar
 * when DIV-style is enabled. By default it is &quot;calendar-&quot;
 * </li>
 * <li>
 * <b>showYearNavigation</b> - Whether or not show year navigation. By default
 * it is FALSE
 * </li>
 * <li>
 * <b>showYearNavigationInput</b> - Whether or not show year navigation input.
 * By default it is FALSE
 * </li>
 * <li>
 * <b>showNavigationDropdowns</b> - Whether or not show navigation drop-downs.
 * By default it is FALSE
 * </li>
 * <li>
 * <b>monthNamesKey</b> - Bundle key to search for localized month names. If it
 * is not specified, calendar defaults are used. If no message was found for
 * specified key, <code>JspException</code> will be thrown
 * </li>
 * <li>
 * <b>daysHeadersKey</b> - Bundle key to search for localized day headers. If it
 * is not specified, calendar defaults are used. If no message was found for
 * specified key, <code>JspException</code> will be thrown
 * </li>
 * <li>
 * <b>weekStartDay</b> - Bundle key to search for week start day. If it is not
 * specified, calendar defaults are used. If no message was found for specified
 * key, <code>JspException</code> will be thrown. Under this key number of day
 * that will be considered first must be stored. For example, if Sunday is first
 * day of week, this number will be 0; if Monday -- then 1 and so on
 * </li>
 * <li>
 * <b>todayTextKey</b> - Bundle key to search for localized 'today' text. If it is
 * not specified, calendar defaults are used. If no message was found for
 * specified key, <code>JspException</code> will be thrown
 * </li>
 * </ul>
 * </p>
 * <p><a href="CalendarTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.4 $ $Date: 2006/08/03 10:08:23 $
 * @jsp.tag name="calendar"
 * body-content="scriptless"
 */
public class CalendarTag extends SimpleTagSupport {

	protected transient final Log log = LogFactory.getLog(CalendarTag.class);

	/**
	 * Full name of input field to put selected date in.
	 * Must be correct JavaScript name such as follows:
	 * document.forms[0].dateInput
	 */
	protected String fieldName;

	/**
	 * Name of anchor that will be generated for calendar popup.
	 * By default it is "jsCalendarAnchor" plus ordinal number
	 * (positive integer leading by zero) of this tag on current page
	 */
	protected String anchorName = null;

	/**
	 * Pattern for displaying date.
	 * By default it is taken from locale stored in session under
	 * org.apache.struts.Globals.LOCALE_KEY
	 * Date pattern taken has only date symbols, not time symbols
	 *
	 * @see org.apache.struts.Globals#LOCALE_KEY
	 */
	protected String datePattern = null;

	/**
	 * Pre-selected start date in calendar according to specified date pattern
	 * By default current date is preselected (it is calendar default)
	 */
	protected String startDate = null;

	/**
	 * Style of calendar displaying.
	 * Valid values are WINDOW and DIV (manner is case insensitive)
	 * Default value is WINDOW
	 */
	protected String style = "window";

	/**
	 * ID of 'div' tag for DIV-style display of calendar.
	 * This attribute is optional if style was set to DIV, otherwise it is ignored
	 * If DIV style was set and this attribute is omitted, default value will be "jsCalendarDiv"
	 * Tag will be generated once, despite on number of calendar tags on current page
	 */
	protected String divStyleId = null;

	/**
	 * CSS prefix to search CSS classes for displaying calendar when DIV-style is enabled
	 * By default it is "calendar-"
	 */
	protected String cssPrefix = "calendar-";

	/**
	 * Whether or not show year navigation
	 * By default it is FALSE
	 */
	protected Boolean showYearNavigation = Boolean.FALSE;

	/**
	 * Whether or not show year navigation input
	 * By default it is FALSE
	 */
	protected Boolean showYearNavigationInput = Boolean.FALSE;

	/**
	 * Whether or not show navigation drop-downs
	 * By default it is FALSE
	 */
	protected Boolean showNavigationDropdowns = Boolean.FALSE;

	/**
	 * Bundle key to search for localized month names
	 * If it is not specified, calendar defaults are used
	 * If no message was found for specified key, JspException will be thrown
	 */
	protected String monthNamesKey = null;

	/**
	 * Bundle key to search for localized day headers
	 * If it is not specified, calendar defaults are used
	 * If no message was found for specified key, JspException will be thrown
	 */
	protected String dayHeadersKey = null;

	/**
	 * Bundle key to search for week start day
	 * If it is not specified, calendar defaults are used
	 * If no message was found for specified key, JspException will be thrown
	 */
	protected String weekStartDayKey = null;

	/**
	 * Bundle key to search for localized today text
	 * If it is not specified, calendar defaults are used
	 * If no message was found for specified key, JspException will be thrown
	 */
	protected String todayTextKey = null;

	protected static final String NUMBER_KEY = "com.blandware.atleap.taglib.CALENDAR_TAG_NUMBER";
	protected static final String DIV_STYLE_ID_KEY = "com.blandware.atleap.taglib.CALENDAR_TAG_DIV_STYLE_ID";

	/**
     * Returns field name
     *
	 * @return field name
     * @see #fieldName
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Full name of input field to put selected date in."
	 */
	public String getFieldName() {
		return fieldName;
	}

    /**
     * Sets field name
     *
     * @param fieldName field name to set
     * @see #fieldName
     */
	public void setFieldName(String fieldName) {
		this.fieldName = fieldName;
	}

	/**
     * Returns anchor name
     *
	 * @return anchor name
     * @see #anchorName
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of anchor that will be generated for calendar popup"
	 */
	public String getAnchorName() {
		return anchorName;
	}

    /**
     * Sets anchor name
     *
     * @param anchorName anchor name to set
     * @see #anchorName
     */
	public void setAnchorName(String anchorName) {
		this.anchorName = anchorName;
	}

	/**
     * Returns date pattern
     *
	 * @return date pattern
     * @see #datePattern
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Pattern for displaying date."
	 */
	public String getDatePattern() {
		return datePattern;
	}

    /**
     * Sets date pattern
     *
     * @param datePattern date pattern to set
     * @see #datePattern
     */
	public void setDatePattern(String datePattern) {
		this.datePattern = datePattern;
	}

	/**
     * Returns start date
     *
	 * @return start date
     * @see #startDate
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Pre-selected start date in calendar according to specified date pattern"
	 */
	public String getStartDate() {
		return startDate;
	}

    /**
     * Sets start date
     *
     * @param startDate start date to set
     * @see #startDate
     */
	public void setStartDate(String startDate) {
		this.startDate = startDate;
	}

	/**
     * Returns style
     *
	 * @return style
     * @see #style
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Style of calendar displaying."
	 */
	public String getStyle() {
		return style;
	}

    /**
     * Sets style
     *
     * @param style style to set
     * @see #style
     */
	public void setStyle(String style) {
		this.style = style;
	}

	/**
     * Returns DIV style ID
     *
	 * @return DIV style ID
     * @see #divStyleId
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="ID of 'div' tag for DIV-style display of calendar"
	 */
	public String getDivStyleId() {
		return divStyleId;
	}

    /**
     * Sets DIV style ID
     *
     * @param divStyleId DIV style ID to set
     * @see #divStyleId
     */
	public void setDivStyleId(String divStyleId) {
		this.divStyleId = divStyleId;
	}

	/**
     * Returns CSS prefix
     *
	 * @return CSS prefix
     * @see #cssPrefix
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="CSS prefix to search CSS classes for displaying calendar when DIV-style is enabled"
	 */
	public String getCssPrefix() {
		return cssPrefix;
	}

    /**
     * Sets CSS prefix
     *
     * @param cssPrefix CSS prefix to set
     * @see #cssPrefix
     */
	public void setCssPrefix(String cssPrefix) {
		this.cssPrefix = cssPrefix;
	}

	/**
     * Returns whether to show year navigation
     *
	 * @return whether to show year navigation
     * @see #showYearNavigation
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether or not show year navigation"
	 */
	public Boolean getShowYearNavigation() {
		return showYearNavigation;
	}

    /**
     * Sets whether to show year navigation
     *
     * @param showYearNavigation whether to show year navigation
     * @see #showYearNavigation
     */
	public void setShowYearNavigation(Boolean showYearNavigation) {
		this.showYearNavigation = showYearNavigation;
	}

	/**
     * Returns whether to show year navigation input
     *
	 * @return whether to show year navigation input
     * @see #showYearNavigationInput
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether or not show year navigation input"
	 */
	public Boolean getShowYearNavigationInput() {
		return showYearNavigationInput;
	}

    /**
     * Sets whether to show year navigation input
     *
     * @param showYearNavigationInput whether to show year navigation input
     * @see #showYearNavigationInput
     */
	public void setShowYearNavigationInput(Boolean showYearNavigationInput) {
		this.showYearNavigationInput = showYearNavigationInput;
	}

	/**
     * Returns whether to show navigation dropups
     *
	 * @return whether to show navigation dropups
     * @see #showNavigationDropdowns
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether or not show navigation drop-downs"
	 */
	public Boolean getShowNavigationDropdowns() {
		return showNavigationDropdowns;
	}

    /**
     * Sets whether to show navigation dropups
     *
     * @param showNavigationDropdowns whether to show navigation dropups
     * @see #showNavigationDropdowns
     */
	public void setShowNavigationDropdowns(Boolean showNavigationDropdowns) {
		this.showNavigationDropdowns = showNavigationDropdowns;
	}

	/**
     * Returns month names bundle key
     *
	 * @return month names bundle key
     * @see #monthNamesKey
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Bundle key to search for localized month names"
	 */
	public String getMonthNamesKey() {
		return monthNamesKey;
	}

    /**
     * Sets month names bundle key
     *
     * @param monthNamesKey month names bundle key to set
     * @see #monthNamesKey
     */
	public void setMonthNamesKey(String monthNamesKey) {
		this.monthNamesKey = monthNamesKey;
	}

	/**
     * Returns day headers bundle key
     *
	 * @return month names bundle key
     * @see #dayHeadersKey
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Bundle key to search for localized day headers"
	 */
	public String getDayHeadersKey() {
		return dayHeadersKey;
	}

    /**
     * Sets day headers bundle key
     *
     * @param dayHeadersKey day headers bundle key to set
     * @see #dayHeadersKey
     */
	public void setDayHeadersKey(String dayHeadersKey) {
		this.dayHeadersKey = dayHeadersKey;
	}

	/**
     * Returns week start day bundle key
     *
	 * @return week start day bundle key
     * @see #weekStartDayKey
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Bundle key to search for week start day"
	 */
	public String getWeekStartDayKey() {
		return weekStartDayKey;
	}

    /**
     * Sets week start day bundle key
     *
     * @param weekStartDayKey week start day bundle key to set
     * @see #weekStartDayKey
     */
	public void setWeekStartDayKey(String weekStartDayKey) {
		this.weekStartDayKey = weekStartDayKey;
	}

	/**
     * Returns today text bundle key
     *
	 * @return today text bundle key
     * @see #todayTextKey
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Bundle key to search for localized today text"
	 */
	public String getTodayTextKey() {
		return todayTextKey;
	}

    /**
     * Sets today text bundle key
     *
     * @param todayTextKey today text bundle key to set
     * @see #todayTextKey
     */
	public void setTodayTextKey(String todayTextKey) {
		this.todayTextKey = todayTextKey;
	}

    /**
     * Performs needed actions: outputs needed HTML code
     *
     * @throws JspException
     * @throws IOException
     */
	public void doTag() throws JspException, IOException {

		PageContext pageContext = (PageContext) getJspContext();
		HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();

		if ( datePattern == null ) {
			datePattern = DateUtil.getDatePattern((Locale) pageContext.getAttribute(Globals.LOCALE_KEY, PageContext.SESSION_SCOPE));
		}

		Integer tagNumber = (Integer) pageContext.getAttribute(NUMBER_KEY);

		if ( tagNumber == null ) {
			tagNumber = new Integer(0);
		} else {
			tagNumber = new Integer(tagNumber.intValue() + 1);
		}

		if ( log.isDebugEnabled() ) {
			log.debug("This tag number will be " + tagNumber);
		}

		pageContext.setAttribute(NUMBER_KEY, tagNumber);

		String var = "jsCalendar" + tagNumber;

		if ( log.isDebugEnabled() ) {
			log.debug("Anchor name: " + anchorName);
		}

		if ( anchorName == null ) {
			anchorName = "jsCalendarAnchor" + tagNumber;
		}

		if ( "div".equalsIgnoreCase(style) ) {
			if ( divStyleId == null ) {
				divStyleId = "jsCalendarDiv";
			}
		} else {
			divStyleId = null;
		}

		if ( log.isDebugEnabled() ) {
			log.debug("Style: " + style + "\tDIV style ID: " + divStyleId);
		}

		String script = "<script language=\"javascript\">\n";
		if ( divStyleId != null ) {
			script += "var " + var + " = new CalendarPopup(\'" + divStyleId + "\');\n";
		} else {
			script += "var " + var + " = new CalendarPopup();\n";
		}
		if ( cssPrefix != null ) {
			script += var + ".setCssPrefix(\'" + cssPrefix + "\');\n";
		}
		if ( showYearNavigation.booleanValue() ) {
			script += var + ".showYearNavigation();\n";
		}

		if ( showYearNavigationInput.booleanValue() ) {
			script += var + ".showYearNavigationInput();\n";
		}

		if ( showNavigationDropdowns.booleanValue() ) {
			script += var + ".showNavigationDropDowns();\n";
		}

		if ( monthNamesKey == null ) {
			monthNamesKey = "core.calendar.monthNames";
		}

		if ( dayHeadersKey == null ) {
			dayHeadersKey = "core.calendar.dayHeaders";
		}

		if ( weekStartDayKey == null ) {
			weekStartDayKey = "core.calendar.weekStartDay";
		}
		if ( todayTextKey == null ) {
			todayTextKey = "core.calendar.todayText";
		}

		TagUtils tagUtils = TagUtils.getInstance();
		ApplicationResources applicationResources = ApplicationResources.getInstance(pageContext.getServletContext());
		String monthNames = applicationResources.getMessage(request, monthNamesKey);
		if ( monthNames == null ) {
			if ( log.isWarnEnabled() ) {
				log.warn("No message for key '" + monthNamesKey + "' was found ");
			}
		} else {
			script += var + ".setMonthNames(" + monthNames + ");\n";
		}

		String dayHeaders = applicationResources.getMessage(request, dayHeadersKey);
		if ( dayHeaders == null ) {
			if ( log.isWarnEnabled() ) {
				log.warn("No message for key '" + dayHeadersKey + "' was found ");
			}
		} else {
			script += var + ".setDayHeaders(" + dayHeaders + ");\n";
		}

		String weekStartDay = applicationResources.getMessage(request, weekStartDayKey);
		if ( weekStartDay == null ) {
			if ( log.isWarnEnabled() ) {
				log.warn("No message for key '" + weekStartDayKey + "' was found ");
			}
		} else {
			script += var + ".setWeekStartDay(" + weekStartDay + ");\n";
		}

		String todayText = applicationResources.getMessage(request, todayTextKey);
		if ( todayText == null ) {
			if ( log.isWarnEnabled() ) {
				log.warn("No message for key " + todayTextKey + " was found ");
			}
		} else {
			script += var + ".setTodayText(\'" + todayText + "\');\n";
		}

		script += "</script>\n";

		StringWriter sw = new StringWriter();
		StringBuffer sb = sw.getBuffer();
		sb.append(script).append("<a href=\"#\" id=\"")
		        .append(anchorName).append("\" name=\"")
		        .append(anchorName).append("\" onclick=\"")
		        .append(var).append(".select(").append(fieldName)
		        .append(", \'").append(anchorName).append("\', \'")
		        .append(datePattern).append("\'")
		        .append(startDate != null ? ", \'" + startDate + "\'" : ");")
		        .append(" return false;\">");

		getJspBody().invoke(sw);
		sb.append("</a>\n");

		if ( style.equalsIgnoreCase("div") && pageContext.getAttribute(DIV_STYLE_ID_KEY) == null ) {
			sb.append("<div id=\"").append(divStyleId).append("\" style=\"visibility: hidden; position: absolute; background-color: white; layer-background-color: white;\"></div>");
			pageContext.setAttribute(DIV_STYLE_ID_KEY, divStyleId);
		}

		tagUtils.write(pageContext, sw.toString());
	}

}
